package com.isyscore.os.metadata.database;

import com.isyscore.os.core.sqlcore.TDBuilder;
import com.isyscore.os.metadata.enums.DataSourceTypeEnum;
import com.isyscore.os.metadata.model.vo.ResultVO;
import com.isyscore.os.metadata.utils.DateUtils;
import org.apache.commons.lang3.StringUtils;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * TD 数据源组件
 * </p>
 *
 * @author : zhan9yn
 * @version : 1.0
 * @date : 2021/11/12 5:44 下午
 */
public class TDDatabase extends AbstractDatabase {

    {
        dbType = DataSourceTypeEnum.TD;
        //时间类型
        DATE_TYPE.add("TIMESTAMP");

        //字段类型
        COLUMN_TYPE.put("NCHAR", "NCHAR(255)");
        COLUMN_TYPE.put("TIMESTAMP", "TIMESTAMP");
        COLUMN_TYPE.put("INT", "INT");
        COLUMN_TYPE.put("BIGINT", "BIGINT");
        COLUMN_TYPE.put("FLOAT", "FLOAT");
        COLUMN_TYPE.put("DOUBLE", "DOUBLE");
        COLUMN_TYPE.put("SMALLINT", "SMALLINT");
        COLUMN_TYPE.put("TINYINT", "TINYINT");
        COLUMN_TYPE.put("BOOL", "BOOL");
        //使用数据类型 binary 或 nchar，需指定其最长的字节数，如 binary(20)，表示 20 字节
        //todo 进行调研后规定合适的默认长度
        COLUMN_TYPE.put("NCHAR", "NCHAR(255)");
        COLUMN_TYPE.put("BINARY", "BINARY(512)");
        builder = new TDBuilder();
    }

    private final String SELECT_TABLE_COL = "DESCRIBE %s";


    /**
     * <p>
     * 可以通过如下语句写入两行记录：
     * <code>INSERT INTO d1001 VALUES ('2021-07-13 14:06:32.272', 10.2, 219, 0.32) (1626164208000, 10.15, 217, 0.33);</code>
     * <br/>
     * TDengine 可以自动识别格式化的时间戳字符串和数值类型的时间戳，故无需做转换
     * </p>
     *
     * @param dateVal description
     * @return java.lang.String
     * @throws
     * @author zhan9yn
     * @date 2021/11/15 10:42 上午
     */
    @Override
    public String getParseStr2DateEl(String dateVal) {
        return "'" + dateVal + "'";
    }

    @Override
    public String getParseDate2StrEl(String dateVal) {
        return null;
    }

    @Override
    public String getParseStr2IntEl(String dateVal) {
        return "'" + dateVal + "'";
    }

    @Override
    public String jdbcUrl(String ip, int port, String dbName, String basicType, String basicValue) {
        return String.format(dbType.getUrl(), ip, port, dbName);
    }

    @Override
    public String tableStructSql(String tableName, String dbName) {
        return String.format(SELECT_TABLE_COL, tableName);
    }

    @Override
    public String tableListSql(String dbName) {
        String SELECT_TABLE = "show stables;";
        return String.format(SELECT_TABLE, dbName);
    }

    @Override
    //目前不支持
    public String linkTableSql(String columns, String dbName) {
        return null;
    }

    @Override
    //目前不支持
    public String renameTableSql(String oldTableName, String newTableName) {
        return null;
    }

    @Override
    public String dropTableSql(String tableName) {
        return String.format("DROP TABLE IF EXISTS `%s`", tableName);
    }

    @Override
    public String pageSql(String sql, int offset, int limit) {
        return sql + " LIMIT " + offset + ", " + limit;
    }

    /**
     * <p>
     * 需要对 BINARY 类型的字段进行转 String 操作<br/>
     * BINARY:
     * 记录单字节字符串，建议只用于处理 ASCII 可见字符，中文等多字节字符需使用 nchar。
     * 理论上，最长可以有 16374 字节，但由于每行数据最多 16K 字节，实际上限一般小于理论值。
     * binary 仅支持字符串输入，字符串两端需使用单引号引用。使用时须指定大小，如 binary(20)
     * 定义了最长为 20 个单字节字符的字符串，每个字符占 1 byte 的存储空间，总共固定占用 20 bytes
     * 的空间，此时如果用户字符串超出 20 字节将会报错。对于字符串内的单引号，可以用转义字符反斜线加单引号来表示，即 \’。
     * </p>
     *
     * @param content    结果集内容
     * @param rs         查询到的结果集
     * @param md         结果集元数据信息
     * @param columnSize description
     * @return void
     * @throws
     * @author zhan9yn
     * @date 2021/11/15 3:17 下午
     */
    @Override
    public void content(List<Map<String, Object>> content, ResultSet rs, ResultSetMetaData md, int columnSize) throws SQLException {
        while (rs.next()) {
            Map<String, Object> value = new HashMap<>();
            for (int i = 1; i <= columnSize; i++) {
                String columnTypeName = md.getColumnTypeName(i);
                Object obj = null;
                if ("TIMESTAMP".equals(columnTypeName) || "DATETIME".equals(columnTypeName)) {
                    obj = DateUtils.format(rs.getTimestamp(i), null);
                } else if ("BINARY".equals(columnTypeName)) {
                    obj = new String(rs.getBytes(i));
                } else {
                    obj = rs.getObject(i);
                }
                value.put(md.getColumnLabel(i), obj);
            }
            content.add(value);
        }
    }


    /**
     * <p>
     * 由于 TD 查询表结构，目前只有 Describe table_name 的语句来进行查询，等到的结果：<br>
     * <blockquote><pre>
     *         Field      |         Type    |   Length  |   Note   |
     *       =============================================================
     *      pkk          | TIMESTAMP       |      8 |      |
     *      col_float    | FLOAT           |     4 |      |
     *     </pre></blockquote>
     * 处理后，只显示非tag的字段
     * 表的第一个字段必须是 TIMESTAMP，并且系统自动将其设为主键；
     * </p>
     *
     * @param result 查询到的初步的结果集
     * @return void
     * @throws
     * @author zhan9yn
     * @date 2021/11/15 3:23 下午
     */
    @Override
    public void postTableStruct(ResultVO result) {
        List<Map<String, Object>> res = result.getContent();
        List<Map<String, Object>> content = res.parallelStream().collect(Collectors.toList());
        for (int i = 0; i < content.size(); i++) {
            Map<String, Object> map = content.get(i);
            String field = (String) map.get("Field");
            map.remove("Field");
            Integer length = (Integer) map.get("Length");
            map.remove("Length");
            String type = (String) map.get("Type");
            map.remove("Type");
            String note = (String) map.get("Note");
            map.remove("Note");

            //重新装配
            map.put("columnComment", StringUtils.isEmpty(note) ? field : note);
            //处理后，只显示非tag的字段 表的第一个字段必须是 TIMESTAMP，并且系统自动将其设为主键；
            map.put("columnKey", i == 0 ? "PRI" : "");
            map.put("columnName", field);
            map.put("columnType", type + "(" + length + ")");
            map.put("dataType", type);
            map.put("oldName", field);
        }
        result.setContent(content);
    }

    @Override
    public void postTableList(ResultVO resultVO) {
        resultVO.getContent().parallelStream().forEach(map -> {
            map.put("tableName", map.get("name"));
        });
    }

    @Override
    public String copyStructSql(String fromTable, String newTable) {
        String sql = "create table %s like %s";
        return String.format(sql,newTable,fromTable);
    }
}
